/***************************************************************************
 *   Copyright (C) 2009 by Geoffrey McRae   *
 *   geoff@spacevs.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#ifdef VIDEO_GTK
#include "gtk.h"

#include <X11/Xlib.h>
#include <gtk/gtk.h>
#include <gtk/gtkgl.h>
#include <gdk/gdkgl.h>
#include <gdk/gdkx.h>
#include <GL/gl.h>
#include <GL/glx.h>
#include <GL/glext.h>

#if !defined(__APPLE__) && !defined(_WIN32)
#define glXGetProcAddress(x) (*glXGetProcAddressARB)((const GLubyte*)x)
PFNGLCREATEPROGRAMOBJECTARBPROC		glCreateProgramObjectARB	= NULL;
PFNGLCREATESHADEROBJECTARBPROC		glCreateShaderObjectARB		= NULL;
PFNGLSHADERSOURCEARBPROC		glShaderSourceARB		= NULL;
PFNGLCOMPILESHADERARBPROC		glCompileShaderARB		= NULL;
PFNGLGETOBJECTPARAMETERIVARBPROC	glGetObjectParameterivARB	= NULL;
PFNGLATTACHOBJECTARBPROC		glAttachObjectARB		= NULL;
PFNGLGETINFOLOGARBPROC			glGetInfoLogARB			= NULL;
PFNGLLINKPROGRAMARBPROC			glLinkProgramARB		= NULL;
PFNGLUSEPROGRAMOBJECTARBPROC		glUseProgramObjectARB		= NULL;
PFNGLGETUNIFORMLOCATIONARBPROC		glGetUniformLocationARB		= NULL;
PFNGLUNIFORM1FARBPROC			glUniform1fARB			= NULL;
PFNGLUNIFORM1IARBPROC			glUniform1iARB			= NULL;
PFNGLACTIVETEXTUREPROC			glActiveTexture			= NULL;
#endif

typedef struct gtkdata {
	GtkWidget	*box;
	GtkWidget	*output;
	bool		bReconfig;
	bool		bSetup;
	unsigned int	w, h, hw, hh;
	unsigned int	outw, outh;
	unsigned int	glw , glh;
	unsigned int 	ox  , oy;
	GLhandleARB	PHandle;

	unsigned int	texy, texu, texv;
} gtkdata_t;

typedef struct gtkframe {
	unsigned int	s;
	void		*y, *u, *v;
} gtkframe_t;

const GLcharARB *FProgram =
  "uniform sampler2DRect Ytex;\n"
  "uniform sampler2DRect Utex,Vtex;\n"
  "void main(void) {\n"
  "  float nx,ny,r,g,b,y,u,v;\n"
  "  vec4 txl,ux,vx;"
  "  nx=gl_TexCoord[0].x;\n"
  "  ny=gl_TexCoord[0].y;\n"
  "  y=texture2DRect(Ytex,vec2(nx,ny)).r;\n"
  "  u=texture2DRect(Utex,vec2(nx/2.0,ny/2.0)).r;\n"
  "  v=texture2DRect(Vtex,vec2(nx/2.0,ny/2.0)).r;\n"

  "  y=1.1643*(y-0.0625);\n"
  "  u=u-0.5;\n"
  "  v=v-0.5;\n"

  "  r=y+1.5958*v;\n"
  "  g=y-0.39173*u-0.81290*v;\n"
  "  b=y+2.017*u;\n"

  "  gl_FragColor=vec4(r,g,b,1.0);\n"
  "}\n";

static bool mpr_video_gtk_new_configure(GtkWidget *output, GdkEventConfigure *event, void *user_data) {
	gtkdata_t *g = user_data;
	XLockDisplay(GDK_DISPLAY());
	g->bReconfig = true;
	XUnlockDisplay(GDK_DISPLAY());
	return true;
}

void *mpr_video_gtk_new(void *data) {
	gtkdata_t* g	= calloc(sizeof(gtkdata_t), 1);
	g->box		= data;
	g->bReconfig	= true;

	#if !defined(__APPLE__) && !defined(_WIN32)
	glCreateProgramObjectARB	= (PFNGLCREATEPROGRAMOBJECTARBPROC	)glXGetProcAddress("glCreateProgramObjectARB"	);
	glCreateShaderObjectARB		= (PFNGLCREATESHADEROBJECTARBPROC	)glXGetProcAddress("glCreateShaderObjectARB"	);
	glShaderSourceARB		= (PFNGLSHADERSOURCEARBPROC		)glXGetProcAddress("glShaderSourceARB"		);
	glCompileShaderARB		= (PFNGLCOMPILESHADERARBPROC		)glXGetProcAddress("glCompileShaderARB"		);
	glGetObjectParameterivARB	= (PFNGLGETOBJECTPARAMETERIVARBPROC	)glXGetProcAddress("glGetObjectParameterivARB"	);
	glAttachObjectARB		= (PFNGLATTACHOBJECTARBPROC		)glXGetProcAddress("glAttachObjectARB"		);
	glGetInfoLogARB			= (PFNGLGETINFOLOGARBPROC		)glXGetProcAddress("glGetInfoLogARB"		);
	glLinkProgramARB		= (PFNGLLINKPROGRAMARBPROC		)glXGetProcAddress("glLinkProgramARB"		);
	glUseProgramObjectARB		= (PFNGLUSEPROGRAMOBJECTARBPROC		)glXGetProcAddress("glUseProgramObjectARB"	);
	glGetUniformLocationARB		= (PFNGLGETUNIFORMLOCATIONARBPROC	)glXGetProcAddress("glGetUniformLocationARB"	);
	glUniform1fARB			= (PFNGLUNIFORM1FARBPROC		)glXGetProcAddress("glUniform1fARB"		);
	glUniform1iARB			= (PFNGLUNIFORM1IARBPROC		)glXGetProcAddress("glUniform1iARB"		);
	glActiveTexture			= (PFNGLACTIVETEXTUREPROC		)glXGetProcAddress("glActiveTexture"		);
	#endif

	GdkGLConfig	*glconfig;

	g->output = gtk_drawing_area_new();
	gtk_widget_set_events(g->output, GDK_EXPOSURE_MASK);
	glconfig = gdk_gl_config_new_by_mode(GDK_GL_MODE_RGB | GDK_GL_MODE_DEPTH | GDK_GL_MODE_DOUBLE);
	if (!glconfig) g_assert_not_reached();
	if (!gtk_widget_set_gl_capability(g->output, glconfig, NULL, TRUE, GDK_GL_RGBA_TYPE))
		g_assert_not_reached();

	g_signal_connect(g->output, "configure-event", G_CALLBACK (mpr_video_gtk_new_configure), g);

	gtk_box_pack_start(GTK_BOX(g->box), g->output, TRUE, TRUE, 0);
	gtk_widget_show(g->output);

	return g;
}

void mpr_video_gtk_free(void *data) {
	gtkdata_t *g = data;
	XLockDisplay(GDK_DISPLAY());

	gtk_widget_destroy(g->output);
	g->output = NULL;
	free(g);

	XUnlockDisplay(GDK_DISPLAY());
}

bool mpr_video_gtk_open(void *data, unsigned int width, unsigned int height, unsigned int actual_width, unsigned int actual_height, unsigned int pix_fmt) {
	gtkdata_t	*g = data;

	XLockDisplay(GDK_DISPLAY());

	g->w	= actual_width;
	g->h	= actual_height;
	g->hw	= actual_width	/ 2;
	g->hh	= actual_height	/ 2;
	g->outw = width;
	g->outh = height;

	XUnlockDisplay(GDK_DISPLAY());
	return true;
}

void mpr_video_gtk_close(void *data) {

}

void mpr_video_gtk_setup(gtkdata_t *g) {
	GLhandleARB	FSHandle;
	int		i;

	glHint(GL_POLYGON_SMOOTH_HINT,GL_NICEST);
	glEnable(GL_TEXTURE_2D);

	g->PHandle	= glCreateProgramObjectARB();
	FSHandle	= glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
	glShaderSourceARB (FSHandle, 1, &FProgram, NULL);
	glCompileShaderARB(FSHandle);

	glAttachObjectARB(g->PHandle,FSHandle);
	glLinkProgramARB(g->PHandle);

	glUseProgramObjectARB(g->PHandle);

	i = glGetUniformLocationARB(g->PHandle, "Ytex");
	glGenTextures(1, &g->texy);
	glBindTexture(GL_TEXTURE_RECTANGLE_ARB, g->texy);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glUniform1iARB(i, 0);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

	i = glGetUniformLocationARB(g->PHandle, "Utex");
	glGenTextures(1, &g->texu);
	glBindTexture(GL_TEXTURE_RECTANGLE_ARB, g->texu);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glUniform1iARB(i, 1);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

	i = glGetUniformLocationARB(g->PHandle, "Vtex");
	glGenTextures(1, &g->texv);
	glBindTexture(GL_TEXTURE_RECTANGLE_ARB, g->texv);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glUniform1iARB(i, 2);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

	g->bSetup = true;
}

bool mpr_video_gtk_queue_new(void *data, AVFrame *pFrame, void **storage) {
	gtkdata_t  *g = data;
	gtkframe_t *f;

	f = malloc(sizeof(gtkframe_t));
	f->s = g->w * g->h;
	f->y = malloc(f->s);
	f->u = malloc(f->s / 2);
	f->v = malloc(f->s / 2);

	*storage = f;
	return true;
}

void mpr_video_gtk_queue_free(void *data, AVFrame *pFrame, void **storage) {
	gtkframe_t *f = *storage;

	free(f->y);
	free(f->u);
	free(f->v);
	free(f);
}

bool mpr_video_gtk_queue(void *data, AVFrame *pFrame, void **storage) {
	gtkdata_t	*g = data;
	gtkframe_t	*f = *storage;
	void		*ptrY, *ptrU, *ptrV;
	unsigned int	y;

	ptrY = f->y;
	ptrU = f->u;
	ptrV = f->v;

	for(y = 0; y < g->h; ++y) {
		memcpy(ptrY, pFrame->data[0] + (y * pFrame->linesize[0]), g->w);
		ptrY += g->w;
	}

	for(y = 0; y < g->hh; ++y) {
		memcpy(ptrU, pFrame->data[1] + (y * pFrame->linesize[1]), g->hw);
		memcpy(ptrV, pFrame->data[2] + (y * pFrame->linesize[2]), g->hw);
		ptrU += g->hw;
		ptrV += g->hw;
	}

	return true;
}

bool mpr_video_gtk_display(void *data, void **storage) {
	gtkdata_t 	*g = data;
	gtkframe_t	*f = *storage;

	XLockDisplay(GDK_DISPLAY());

	GdkGLContext	*glcontext	= gtk_widget_get_gl_context	(g->output);
	GdkGLDrawable	*gldrawable	= gtk_widget_get_gl_drawable	(g->output);

	if (!gdk_gl_drawable_gl_begin (gldrawable, glcontext))
		g_assert_not_reached();

	if (g->bReconfig) {
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glViewport(0, 0, g->output->allocation.width, g->output->allocation.height);
		glOrtho   (0, g->output->allocation.width, g->output->allocation.height, 0, -1, 1);
		if (!g->bSetup) mpr_video_gtk_setup(g);

		/* keep the aspect ratio correct */
		if (g->outw / g->outh > g->output->allocation.width / g->output->allocation.height) {
			g->glw = g->output->allocation.width;
			g->glh = g->outh * g->output->allocation.width  / g->outw;
		} else {
			g->glw = g->outw * g->output->allocation.height / g->outh;
			g->glh = g->output->allocation.height;
		}

		g->ox = (g->output->allocation.width  / 2) - (g->glw / 2);
		g->oy = (g->output->allocation.height / 2) - (g->glh / 2);

		g->bReconfig = false;

	}

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_RECTANGLE_ARB, g->texy);
	glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, g->w , g->h , 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, f->y);

	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_RECTANGLE_ARB, g->texu);
	glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, g->hw, g->hh, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, f->u);

	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_RECTANGLE_ARB, g->texv);
	glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_LUMINANCE, g->hw, g->hh, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, f->v);	

	glPushMatrix();
	glTranslated(g->ox, g->oy, 0);
	glBegin(GL_QUADS);
        glTexCoord2i	(0     , 0     );
        glVertex2i	(0     , 0     );
        glTexCoord2i	(g->w  , 0     );
        glVertex2i	(g->glw, 0     );
        glTexCoord2i	(g->w  , g->h  );
        glVertex2i	(g->glw, g->glh);
        glTexCoord2i	(0     , g->h  );
        glVertex2i	(0     , g->glh);
	glEnd();
	glPopMatrix();

	if (gdk_gl_drawable_is_double_buffered (gldrawable))
		gdk_gl_drawable_swap_buffers (gldrawable);
	else	glFlush();

	gdk_gl_drawable_gl_end(gldrawable);

	XUnlockDisplay(GDK_DISPLAY());
	return true;
}

void mpr_video_gtk_drop(void *data, void **storage) {
}

#endif